﻿using System;
using System.Globalization;
using System.Collections.Generic;

namespace FscNET.Tracing
{

    public class TraceEntry
    {
        #region fields and properties
        private static readonly char[] _hexPrefix = new char[] { '0', 'x' };
        private static readonly char[] _commaPrefix = new char[] { '.' };
        private static readonly string[] _entrySplitter = new string[] { ": " };
        private static readonly char[] _metaSplitter = new char[] { '|' };
        private static readonly char[] _nullChar = new char[] { '\0' };

        private static readonly NumberStyles _hexNumStyle = NumberStyles.HexNumber | NumberStyles.AllowHexSpecifier;
        private int _sessionId = System.Diagnostics.Process.GetCurrentProcess().SessionId;


        private readonly TraceCategory _category;
        public TraceCategory Category
        {
            get { return _category; }
            //set { _category = value; }
        }
        private readonly int _process;
        public int Process
        {
            get { return _process; }
            //set { _process = value; }
        }
        private readonly int _thread;
        public int Thread
        {
            get { return _thread; }
            //set { _thread = value; }
        }
        private readonly string _module;
        public string Module
        {
            get { return _module; }
            //set { _module = value; }
        }
        private readonly string _project;
        public string Project
        {
            get { return _project; }
            //set { _project = value; }
        }
        private readonly string _function;
        public string Function
        {
            get { return _function; }
            //set { _function = value; }
        }
        private readonly string _file;
        public string File
        {
            get { return _file; }
            //set { _file = value; }
        }
        private readonly int _line;
        public int Line
        {
            get { return _line; }
            //set { _line = value; }
        }
        private readonly int _stack;
        public int Stack
        {
            get { return _stack; }
            //set { _stack = value; }
        }
        private readonly string _user;
        public string User
        {
            get { return _user; }
            //set { _user = value; }
        }
        private readonly string _message;
        public string Message
        {
            get { return _message; }
            //set { _message = value; }
        }


        private static ProcessTableDict _processTable = new ProcessTableDict();
        #endregion

        public TraceEntry(string s)
        {
            try
            {
                int processId, threadId;
                if (string.IsNullOrEmpty(s)) return;
                string[] parts = s.Split(_entrySplitter, 2, StringSplitOptions.RemoveEmptyEntries);
                if (parts.Length == 0) { _message = "Invalid entry"; return; }
                if (parts.Length == 1) { _message = parts[0]; return; }
                _message = parts[1].TrimEnd(_nullChar);

                string[] meta = parts[0].Split(_metaSplitter/*, StringSplitOptions.RemoveEmptyEntries*/);
                if (meta.Length == 0) return;
                _category = StringToTraceCategory(meta[0]);

                //messages comming from processes running in different session than tracer have default output
                if (meta.Length == 4)
                {
                    bool pos1IsInt = int.TryParse(meta[1].TrimStart(_hexPrefix), _hexNumStyle, CultureInfo.InvariantCulture, out processId);
                    bool pos2IsInt = int.TryParse(meta[2].TrimStart(_hexPrefix), _hexNumStyle, CultureInfo.InvariantCulture, out threadId);
                    
                    if(pos1IsInt && pos2IsInt)
                    {
                        ProcessTalbeEntry processInfo = _processTable[processId];
                        if(OutputManager.TraceOutput == TraceOutput.Default ||
                        processInfo.Valid && processInfo.SessionId != _sessionId
                        /*|| meta.Length - 1 != OutputManager.OutputCount*/)
                        {
                            _process = processId;
                            _thread = threadId;
                            _module = meta[3];
                        }
                    }
                    //else if (OutputManager.OutputCount == 3 && pos1IsInt && pos2IsInt)
                    //{
                    //    //worst case, don't know what output #4 represents
                    //    _process = processId;
                    //    _thread = threadId;
                    //    //for now, if it is w3wp, it's module (trace with default output)
                    //    if (meta[3].Length == 4 && meta[3] == "w3wp") _module = meta[3];
                    //    else
                    //    {
                    //    }
                    //}
                }
                else if (meta.Length == 10)
                {
                    int idx = 1;
                    int.TryParse(meta[idx++].TrimStart(_hexPrefix), _hexNumStyle, CultureInfo.InvariantCulture, out _process);
                    int.TryParse(meta[idx++].TrimStart(_hexPrefix), _hexNumStyle, CultureInfo.InvariantCulture, out _thread);
                    _module = meta[idx++];
                    _project = meta[idx++];
                    _function = meta[idx++];
                    _file = meta[idx++];
                    int.TryParse(meta[idx++].TrimStart(_commaPrefix), NumberStyles.None, CultureInfo.InvariantCulture, out _line);
                    int.TryParse(meta[idx++], NumberStyles.None, CultureInfo.InvariantCulture, out _stack);
                    _user = meta[idx];
                }
                else //no corner case, parts match output settings
                {
                    int idx = 1;

                    if (OutputManager.Process)
                        int.TryParse(meta[idx++].TrimStart(_hexPrefix), _hexNumStyle, CultureInfo.InvariantCulture, out _process);
                    if (OutputManager.Thread)
                        int.TryParse(meta[idx++].TrimStart(_hexPrefix), _hexNumStyle, CultureInfo.InvariantCulture, out _thread);
                    if (OutputManager.Module) _module = meta[idx++];
                    if (OutputManager.Project) _project = meta[idx++];
                    if (OutputManager.Function) _function = meta[idx++];
                    if (OutputManager.File) _file = meta[idx++];
                    if (OutputManager.Line)
                        int.TryParse(meta[idx++].TrimStart(_commaPrefix), NumberStyles.None, CultureInfo.InvariantCulture, out _line);
                    if (OutputManager.Stack)
                        int.TryParse(meta[idx++], NumberStyles.None, CultureInfo.InvariantCulture, out _stack);
                    if (OutputManager.User) _user = meta[idx];
                }
            }
            catch (Exception e)
            {
            }
        }

        //private static string GetProcessNameByPID(int pid)
        //{
        //    try
        //    {
        //        System.Diagnostics.Process p = System.Diagnostics.Process.GetProcessById(pid);
        //        if (p != null)
        //            return p.ProcessName;
        //        return "unknown";
        //    }
        //    catch
        //    {
        //        return "unknown";
        //    }
        //}

        private static TraceCategory StringToTraceCategory(string categoryStr)
        {
            if (categoryStr == null || categoryStr.Length != 2) return 0;
            switch (categoryStr)
            {
                case "EA": return TraceCategory.EA;
                case "EE": return TraceCategory.EE;
                case "EM": return TraceCategory.EM;
                case "ES": return TraceCategory.ES;
                case "EW": return TraceCategory.EW;
                case "EX": return TraceCategory.EX;
                case "CA": return TraceCategory.CA;
                case "CC": return TraceCategory.CC;
                case "CM": return TraceCategory.CM;
                case "CS": return TraceCategory.CS;
                case "CW": return TraceCategory.CW;
                case "CX": return TraceCategory.CX;
                case "KA": return TraceCategory.KA;
                case "KK": return TraceCategory.KK;
                case "KM": return TraceCategory.KM;
                case "KS": return TraceCategory.KS;
                case "KW": return TraceCategory.KW;
                case "KX": return TraceCategory.KX;
                case "TA": return TraceCategory.TA;
                case "TT": return TraceCategory.TT;
                case "TM": return TraceCategory.TM;
                case "TS": return TraceCategory.TS;
                case "TW": return TraceCategory.TW;
                case "TX": return TraceCategory.TX;
                case "FA": return TraceCategory.FA;
                case "FF": return TraceCategory.FF;
                case "FM": return TraceCategory.FM;
                case "FS": return TraceCategory.FS;
                case "FW": return TraceCategory.FW;
                case "FX": return TraceCategory.FX;
                case "II": return TraceCategory.II;
                default:

                    return 0;
            }
        }

        public static implicit operator TraceEntry(string s)
        {
            return new TraceEntry(s);
        }

        internal class ProcessTableDict
        {
            private Dictionary<int, ProcessTalbeEntry> _table = new Dictionary<int, ProcessTalbeEntry>(16);

            public ProcessTalbeEntry this[int i]
            {
                get
                {
                    ProcessTalbeEntry e;
                    if (_table.TryGetValue(i, out e)) return e;
                    try
                    {
                        System.Diagnostics.Process p = System.Diagnostics.Process.GetProcessById(i);

                        return _table[i] = p == null
                            ? ProcessTalbeEntry.Invalid
                            : new ProcessTalbeEntry(p.ProcessName, p.SessionId);
                    }
                    catch
                    {
                        return _table[i] = ProcessTalbeEntry.Invalid;
                    }
                }
            }
        }

        internal struct ProcessTalbeEntry
        {
            private string _name;

            //highest bit marks validity
            private byte _data;

            public ProcessTalbeEntry(string name, int session)
            {
                _data = 0;
                _name = name ?? "";
                _data = (byte)(session > 127
                    ? 128 // invalid entry, session number too high
                    : 127 & session | 128 & _data);
            }

            public static ProcessTalbeEntry Invalid { get { return new ProcessTalbeEntry("", 128); } }

            public int SessionId { get { return 127 & _data; } }
            public string Name { get { return _name ?? ""; } }
            public bool Valid { get { return (_data >> 7) == 0; } }
        }
    }
}

